#region AppServices License

// Product: 
// AppServices
// http://appservices.codeplex.com
//
// Author:
// Pawel Idzikowski
// http://www.polenter.com
// mailto:idzikowski@polenter.com?subject=AppServices-Question
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//    1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//
//    2. Altered source versions must be plainly marked as such, and must not
//      be misrepresented as being the original software.
//
//    3. This notice may not be removed or altered from any source distribution.

#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using Polenter.Services.Advanced;
using Polenter.Services.Internal;

namespace Polenter.Services
{
    /// <summary>
    /// Service-Container. It can be used as Singleton (AppServices.Instance.AddService...) 
    /// or a normal container (new AppServices().AddService...)
    /// </summary>
    public class AppServices
    {
        private static AppServices _instance;

        private readonly ServiceInfoCollection _services = new ServiceInfoCollection();

        /// <summary>
        /// Singleton container for services
        /// </summary>
        public static AppServices Instance
        {
            get
            {
                if (_instance == null) _instance = new AppServices();
                return _instance;
            }
        }

        ///<summary>
        ///</summary>
        ///<param name="service"></param>
        ///<param name="serviceType">It could be service type or a base type of the service, i.e. interface or abstract class</param>
        public void AddService(object service, Type serviceType)
        {
            _services.Add(new ServiceInfo(serviceType, service));
        }

        ///<summary>
        ///</summary>
        ///<param name="service"></param>
        ///<typeparam name="T">It could be base type of the service, i.e. interface or abstract class</typeparam>
        public void AddService<T>(T service) where T : class
        {
            AddService(service, typeof (T));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serviceType"></param>
        /// <returns>true if service found and removed</returns>
        public bool RemoveService(Type serviceType)
        {
            if (serviceType == null) throw new ArgumentNullException("serviceType");
            if (_services.Contains(serviceType))
            {
                _services.Remove(serviceType);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>true if service found and removed</returns>
        public bool RemoveService<T>() where T : class
        {
            return RemoveService(typeof (T));
        }

        /// <summary>
        /// </summary>
        /// <param name = "serviceType"></param>
        /// <returns>null if nothing found, otherweise the service instance</returns>
        public object GetService(Type serviceType)
        {
            if (serviceType == null) throw new ArgumentNullException("serviceType");
            if (_services.Contains(serviceType))
            {
                return _services[serviceType].Service;
            }

            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>null if nothing found</returns>
        public T GetService<T>() where T : class
        {
            Type type = typeof (T);
            return (T) GetService(type);
        }

        /// <summary>
        /// Enumerate all Services
        /// </summary>
        /// <returns></returns>
        public ServiceInfo[] GetServices()
        {
            return new List<ServiceInfo>(_services).ToArray();
        }

        /// <summary>
        /// Fill all public and non public properties and fields which are marked with ServiceAttribute, for all services in the container
        /// </summary>
        public void InjectServices()
        {
            InjectServices(false);
        }

        /// <summary>
        /// Fill all public and non public properties and fields which are marked with ServiceAttribute, for all services in the container
        /// </summary>
        /// <param name="injectOnlyNullMembers">if true, only these members are injected which are null</param>
        public void InjectServices(bool injectOnlyNullMembers)
        {
            var serviceList = new List<ServiceInfo>(_services);
            serviceList.Sort(new ServiceInfoComparer());

            foreach (ServiceInfo target in serviceList)
            {
                InjectServices(target.Service, injectOnlyNullMembers);
            }
        }

        /// <summary>
        /// Fill all public and non public properties and fields of target which are marked with ServiceAttribute
        /// </summary>
        public void InjectServices(object target)
        {
            InjectServices(target, false);
        }

        /// <summary>
        /// Fill all public and non public properties and fields of target which are marked with ServiceAttribute
        /// </summary>
        /// <param name="target"></param>
        /// <param name="injectOnlyNullMembers">if true, only these members are injected which are null</param>
        public void InjectServices(object target, bool injectOnlyNullMembers)
        {
            if (target == null) return;
            var injector = new ServiceInjector(this, injectOnlyNullMembers);

            var injectedMemberProvider = new InjectedMemberProvider();
            InjectedMemberInfo memberInfo = injectedMemberProvider.GetInjectedMembers(target.GetType());
            foreach (FieldInfo field in memberInfo.Fields)
            {
                injector.InjectService(field, target);
            }

            foreach (PropertyInfo property in memberInfo.Properties)
            {
                injector.InjectService(property, target);
            }
        }
    }

    /// <summary>
    /// Every property or field marked with this attribute can be injected with a service in the method AppServices.InjectServices
    /// </summary>
#if !SILVERLIGHT
    [SerializableAttribute]
#endif
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    [AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = false
        )]
    public sealed class ServiceAttribute : Attribute
    {
    }
}